/**
* XMLWriter - Class to write a given Document to an OutputStream or Writer.
*
* Copyright (c) 2001
* Marty Phelan, All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.taursys.dom;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.io.PrintWriter;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.DocumentType;
/**
* This class is used to write a given Document to an OutputStream or Writer.
*/
public class XMLWriter extends AbstractWriter {
protected PrintWriter printWriter;
/**
* Default constructor.
*/
public XMLWriter() {
}
/**
* Writes the specified Document to the OutputStream.
* @param doc the <code>Document</code> to write
* @param stream the <code>OutputStream</code> to write to
*/
public void write(Document doc, OutputStream stream) {
Writer writer = new OutputStreamWriter(stream);
printWriter = new PrintWriter(writer);
write(doc);
}
/**
* Writes the specified Document to the Writer.
* @param doc the <code>Document</code> to write
* @param writer the <code>Writer</code> to write to
*/
public void write(Document doc, Writer writer) {
printWriter = writer instanceof PrintWriter
? (PrintWriter)writer : new PrintWriter(writer);
write(doc);
}
// *******************************************************
// Protected Methods
// *******************************************************
/**
* Write the specified node, recursively. This implementation will
* write the following type <code>Nodes</code>:
* <ul>
* <li>DOCUMENT_NODE - If the <code>Document</code> <code>version</code>
* or <code>encoding</code> properties are set, then it will produce an
* initial XML processing instructions node. If the <code>docType</code>
* property is not null, it will produce a DOCTYPE node.</li>
* <li>ELEMENT_NODE - invokes the <code>writeElementNode</code> to write the
* given element node.</li>
* <li>COMMENT_NODE - writes comment node.</li>
* <li>ENTITY_REFERENCE_NODE - writes entity node.</li>
* <li>CDATA_SECTION_NODE - writes CDATA node.</li>
* <li>TEXT_NODE - invokes <code>normalizeAndPrint</code> to write the
* contents of a text node.</li>
* <li>PROCESSING_INSTRUCTION_NODE - writes processing node.</li>
* </ul>
* @param node the starting node to write and recurse.
*/
protected void write(Node node) {
// is there anything to do?
if (node == null) {
return;
}
short type = node.getNodeType();
switch (type) {
case Node.DOCUMENT_NODE: {
Document document = (Document)node;
// Print DOCTYPE if defined
if (document.getDoctype() != null) {
DocumentType docType = document.getDoctype();
printWriter.print("<!DOCTYPE");
printWriter.print(" " + docType.getName());
printWriter.print(" PUBLIC \"" + docType.getPublicId() + "\"");
printWriter.print(" \"" + docType.getSystemId() + "\"");
printWriter.println(">");
printWriter.flush();
}
write(document.getDocumentElement());
break;
}
case Node.ELEMENT_NODE: {
writeElementNode(node);
break;
}
case Node.COMMENT_NODE: {
printWriter.print("<!--");
String data = node.getNodeValue();
if (data != null && data.length() > 0) {
printWriter.print(' ');
printWriter.print(data);
}
printWriter.println("-->");
printWriter.flush();
break;
}
case Node.ENTITY_REFERENCE_NODE: {
printWriter.print('&');
printWriter.print(node.getNodeName());
printWriter.print(';');
printWriter.flush();
break;
}
case Node.CDATA_SECTION_NODE: {
printWriter.print("<![CDATA[");
printWriter.print(node.getNodeValue());
printWriter.print("]]>");
printWriter.flush();
break;
}
case Node.TEXT_NODE: {
normalizeAndPrint(node.getNodeValue());
printWriter.flush();
break;
}
case Node.PROCESSING_INSTRUCTION_NODE: {
printWriter.print("<?");
printWriter.print(node.getNodeName());
String data = node.getNodeValue();
if (data != null && data.length() > 0) {
printWriter.print(' ');
printWriter.print(data);
}
printWriter.println("?>");
printWriter.flush();
break;
}
}
}
/**
* Write the given element node recursively. This method first checks to
* see if the given element node has any children. If it does NOT have any
* children, it invokes the <code>writeEmptyElementNode</code> method.
* Otherwise, it writes the element node itself. It begins by invoking the
* <code>writeAttributes</code> method to generate the attributes for the
* element node. It then invokes the <code>write</code> method for each child.
* Finally it writes the end tag for the node.
* @param node the element node to recursively write.
*/
protected void writeElementNode(Node node) {
Node child = node.getFirstChild();
if (child != null) {
printWriter.print('<');
printWriter.print(node.getNodeName());
writeAttributes(node);
printWriter.print('>');
printWriter.flush();
while (child != null) {
write(child);
child = child.getNextSibling();
}
printWriter.print("</" + node.getNodeName() + '>');
} else {
writeEmptyElementNode(node);
}
printWriter.flush();
}
/**
* Writes the given empty element node. This implementation simply writes the
* opening tag with attributes and then the closing tag. It does not use the
* empty tag shorthand method. It uses the <code>writeAttributes</code> method
* to generate the attributes for the node.
* @param node the element node to write
*/
protected void writeEmptyElementNode(Node node) {
printWriter.print('<');
printWriter.print(node.getNodeName());
writeAttributes(node);
printWriter.print("></" + node.getNodeName() + '>');
}
/**
* Writes the attributes for the given node in alphabetic order.
* All attributes are written as attributeName="value". The value is
* normalized before it is written.
* @param node the node with the attributes to write.
* @see #normalizeAndPrint
*/
protected void writeAttributes(Node node) {
Attr attrs[] = sortAttributes(node.getAttributes());
for (int i = 0; i < attrs.length; i++) {
Attr attr = attrs[i];
printWriter.print(' ');
printWriter.print(attr.getNodeName());
printWriter.print("=\"");
normalizeAndPrint(attr.getNodeValue());
printWriter.print('"');
}
}
/**
* Returns a sorted list of attributes for the given map.
* @param attrs the attribute map to sort
* @return an <code>Attr[]</code> of sorted attributes
*/
protected Attr[] sortAttributes(NamedNodeMap attrs) {
int len = (attrs != null) ? attrs.getLength() : 0;
Attr array[] = new Attr[len];
for (int i = 0; i < len; i++) {
array[i] = (Attr)attrs.item(i);
}
for (int i = 0; i < len - 1; i++) {
String name = array[i].getNodeName();
int index = i;
for (int j = i + 1; j < len; j++) {
String curName = array[j].getNodeName();
if (curName.compareTo(name) < 0) {
name = curName;
index = j;
}
}
if (index != i) {
Attr temp = array[i];
array[i] = array[index];
array[index] = temp;
}
}
return array;
}
/**
* Normalizes and prints the given string. This method normalizes each
* character. It replaces any special characters with the &XX; notation.
* @param s the <code>String</code> to normalize and print
*/
protected void normalizeAndPrint(String s) {
int len = (s != null) ? s.length() : 0;
for (int i = 0; i < len; i++) {
char c = s.charAt(i);
normalizeAndPrint(c);
}
}
/**
* Normalizes and print the given character. It replaces any special
* characters with the &XX; notation.
* @param c the <code>char</code> to normalize and print
*/
protected void normalizeAndPrint(char c) {
switch (c) {
case '<': {
printWriter.print("<");
break;
}
case '>': {
printWriter.print(">");
break;
}
case '&': {
printWriter.print("&");
break;
}
case '"': {
printWriter.print(""");
break;
}
case '\r':
case '\n': {
}
default: {
printWriter.print(c);
}
}
}
}